import json
import os
import argparse
from multiprocessing import Process
from shutil import copyfile
import time
from typing import *
import re

DEBUG = False


def replace_lines_args(source: List[str], **kwargs) -> List[str]:
    def replace(line: str) -> str:
        for key in kwargs:
            line = line.replace(f"${key}", kwargs[key])
        return line
    return [replace(line) for line in source]

def replace_file_args(source: str, dest: str, kwargs: dict):
    lines = []
    with open(source, "r", encoding="utf-8") as f:
        lines = f.readlines()
    for i in range(len(lines)):
        line = lines[i]
        for key in kwargs:
            line = line.replace(f"%{key}%", str(kwargs[key]))
        # print(line)
        lines[i] = line
    with open(dest, "w", encoding="utf-8") as f:
        f.writelines(lines)


def run_one(module: str, root_dir: str,
            create_only: bool = True,
            synthesis: bool = True,
            implementation: bool = False,
            time_report: bool = True,
            generate_fdc: bool = False):
    if create_only and (synthesis or implementation or time_report):
        raise Exception("Args error")
    if time_report and not (synthesis or implementation):
        raise Exception("Args error")
    executable = 'create_project' if create_only else 'run_synth' if synthesis else 'run_impl'
    os.chdir(root_dir)
    work_dir = os.path.abspath(os.path.join(root_dir, f"pds/{module}"))
    if not os.path.exists(work_dir):
        os.mkdir(work_dir)
    for filename in os.listdir(os.path.join(root_dir, "scripts/tcl-pds")):
        if filename.endswith(".tcl"):
            replace_file_args(os.path.join(os.path.join(root_dir, "scripts/tcl-pds"),
                     filename), os.path.join(work_dir, filename), {
                         "generate_timng_report": time_report,
                         "top": module
                     })
    if generate_fdc:
        with open("scripts/fdc/templates/input.fdc") as f:
            template_input = f.readlines()
        with open("scripts/fdc/templates/output.fdc") as f:
            template_output = f.readlines()
        with open(f"build/chisel-rtl/{module}Wrapper.v") as f:
            source = "\t".join(f.readlines())
        ignore_names = ["clock", "resetN"]
        with open(f"scripts/fdc/{module}.json", "r", encoding="utf-8") as f:
            pins = json.load(f)
        with open(os.path.join(work_dir, "generated.fdc"), "w", encoding="utf-8") as f:
            res = re.compile("module " + module + r"Wrapper\(\n(.*?)\)", re.DOTALL).findall(source)
            if len(res) == 0:
                raise RuntimeError("parse verilog error")
            port_lines = [line for line in res[0].splitlines() if len(line.lstrip()) != 0]
            for line in port_lines:
                res = re.match(r"^\s*(\w+)+\s*(\[.*\:0\])*?\s*(\w+)+", line)
                if res is None:
                    raise RuntimeError("parse verilog error")
                groups = res.groups()
                if DEBUG:
                    print(line, '\t', res, groups)
                typ, bit, name = groups
                if name in ignore_names:
                    continue
                bit = int(bit[1:-3]) if bit is not None else 1
                def write_one(name: str, pin: int):
                    if typ == 'input':
                        f.writelines(replace_lines_args(template_input, pin=pin, name=name))
                    elif typ == 'output':
                        f.writelines(replace_lines_args(template_output, pin=pin, name=name))
                    else:
                        raise RuntimeError(f"Unknown direction: {typ}")
                if bit != 1:
                    for i in range(len(pins[name])):
                        write_one(f"{name}[{i}]", pins[name][i])
                else:
                    write_one(name, pins[name])
    os.chdir(work_dir)
    time_start = time.time()
    print(f"working dir: {os.getcwd()}")
    command = f"pds_shell -f " + \
        os.path.join(work_dir, f"{executable}.tcl") + \
        f" -work_dir {work_dir} -project_name {module}"
    print(command)
    os.system(command)
    time_end = time.time()
    print(
        f"\t=============== module {module} finished in {(time_end - time_start):.3f}s ===============")
    # if time_report:
    #     time_report_filenames = ["timing_report_synth.txt", "timing_report_impl.txt"]
    #     time_report_path = f"../../../pds/{module}/"
    #     for filename in time_report_filenames:
    #         filepath = os.path.join(time_report_path, filename)
    #         if os.path.exists(filepath):
    #             print("\t===============", module, filename, "===============")
    #             with open(filepath, "r", encoding='utf8') as f:
    #                 lines = f.readlines()
    #                 for i in range(len(lines)):
    #                     line = lines[i]
    #                     if line.startswith("| Design Timing Summary"):
    #                         for j in range(6):
    #                             l = lines[i + 4 + j].replace("\n", "")
    #                             if len(l) != 0:
    #                                 print(l)
    #                         break


def main(*modules, **kwargs):
    if os.path.exists("scripts"):
        os.chdir("scripts")
    root_dir = os.path.abspath(os.path.join(os.getcwd(), ".."))
    if len(modules) == 0:
        files = [f.replace(".v", "") for f in os.listdir("../build/chisel-rtl") if f.endswith(".v") and not f.endswith(
            "Wrapper.v") and os.path.exists("../build/chisel-rtl/" + f.replace('.v', '') + "Wrapper.v")]
        print(f"all modules: {files}")
        modules = files
    try:
        # build_one(module, root_dir, create_only=create_only)
        # for multi-threads
        processes = [Process(target=run_one, args=(
            module, root_dir), kwargs=kwargs) for module in modules]
        [process.start() for process in processes]
        [process.join() for process in processes]
    except Exception as e:
        raise e
    finally:
        [os.remove(filename) for filename in os.listdir(".")
         if filename.endswith(".tcls") or 'backup' in filename]
        os.chdir(root_dir)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Build modules via PDS.')
    parser.add_argument('modules', type=str, nargs="*",
                        help=f"optional list for modules to generate")
    parser.add_argument('-c', dest='create_only', action="store_true",
                        help='create projects only')
    parser.add_argument('-s', dest='synthesis', action="store_true",
                        help='create project and run to synthesis')
    parser.add_argument('-i', dest='implementation', action="store_true",
                        help='create project and run to implementation')
    parser.add_argument('-t', dest='time_report', action="store_true",
                        help='report timing summary')
    parser.add_argument('-g', dest='generate_fdc', action="store_true",
                        help='generate fdc file from Verilog')
    args = parser.parse_args()
    main(*args.modules,
         create_only=args.create_only,
         synthesis=args.synthesis,
         implementation=args.implementation,
         time_report=args.time_report,
         generate_fdc=args.generate_fdc)
